package ace.module.weixin.core.impl.service.impl;

import ace.module.weixin.core.impl.manager.WxsLockManager;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import lombok.SneakyThrows;

/**
 * .
 *
 * @author Caspar
 */
public class DbLock implements Lock {

  private final String key;
  private final String ownerId;
  private final WxsLockManager wxsLockManager;

  public DbLock(String key, WxsLockManager wxsLockManager) {
    this.key = key;
    this.wxsLockManager = wxsLockManager;
    this.ownerId = UUID.randomUUID().toString().replaceAll("-", "");
  }

  @Override
  public void lock() {
    if (this.lock(-1, TimeUnit.HOURS, false, false)) {
      return;
    }
    throw new IllegalStateException();
  }

  @Override
  public void lockInterruptibly() throws InterruptedException {
    if (this.lock(-1, TimeUnit.HOURS, true, false)) {
      return;
    }
    throw new IllegalStateException();
  }

  @Override
  public boolean tryLock() {
    return this.lock(100, TimeUnit.MILLISECONDS, false, true);
  }

  @Override
  public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
    return this.lock(time, unit, true, true);
  }

  @Override
  public void unlock() {
    this.wxsLockManager.unlock(key, ownerId);
  }

  @Override
  public Condition newCondition() {
    throw new UnsupportedOperationException("DbLock unsupported newCondition method");
  }

  @SneakyThrows
  private boolean lock(
      long leaseTime,
      TimeUnit timeUnit,
      boolean interruptedFlag,
      boolean immediatelyFlag
  ) {
    boolean lockFlag = false;
    long expireTime = System.currentTimeMillis() + timeUnit.toMillis(leaseTime);
    while (System.currentTimeMillis() <= expireTime || leaseTime < 0) {
      if (interruptedFlag && Thread.interrupted()) {
        throw new InterruptedException();
      }
      lockFlag = wxsLockManager.lock(key, ownerId);
      if (immediatelyFlag) {
        return lockFlag;
      }
      Thread.sleep(500);
    }
    return lockFlag;
  }
}
